package com.tencent.api.utils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 封装Dao数据库重复代码
 * 封装两个方法：
 *      一个简化非DQL
 *      一个简化DQL
 */
public abstract class BaseDao {

    /**
     * 封装简化非DQL语句
     * @param sql       带占位符的SQL语句
     * @param params    占位符的值,注意，传入SQL语句的值和SQL语句中？的位置要一一对应
     * @return          执行影响的行数
     */
    public int executeUpdate(String sql,Object ... params) throws SQLException {
        Connection connection = JDBCUtilsV2.getConnection();

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //可变参数可以当做数组使用
        if (params != null && params.length !=0){
            for (int i = 1; i <= params.length; i++) {
                preparedStatement.setObject(i,params[i-1]);
            }
        }


        int rows = preparedStatement.executeUpdate();

        preparedStatement.close();

        //是否回收连接，需要考虑是不是事务
//        connection.setAutoCommit(false);//开启事务了，不要关连接了，业务层service处理
        if (connection.getAutoCommit()) {
            //没有开启事务，正常回收连接
            connection.close();
        }

        return rows;
    }

    /**
     * 非DQL语句封装方法的返回值固定为int
     *
     * DQL语句封装方法的返回值是什么呢？  List<T>
     *
     *          并不是list<Map> map    key和value自定义，不能也不用先设定好
     *                         map 没有校验机制
     *                         map 不支持反射操作
     *
     *          数据库数据应该和java的实体类对应
     *
     *          table
     *              t_user
     *                  id
     *                  account
     *                  password
     *                  nickname
     *          java
     *              User
     *                  id
     *                  account
     *                  password
     *                  nickname
     *          表中一行应对应java实体类中的一个对象，多行对应List<java实体类> list
     *
     *   <T>声明一个泛型，不确定类型
     *      1.确定泛型 User.class   T = class
     *      2.要使用反射技术属性赋值
     *
     *
     * 查询结果封装到一个实体类集合
     * @param clazz 要接值的实体类集合的模板对象
     * @param sql 查询语句，要求列名或者别名等于实体类的属性名！！！一定要格外注意
     * @param parameters 占位符的值，要和？位置对应
     * @param <T> 声明的结果的类型
     * @return 声明的结果的类型
     */
    public <T> List<T> executeQuery(Class<T> clazz,String sql,Object ... parameters) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException {


        Connection connection = JDBCUtilsV2.getConnection();

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        if (parameters != null && parameters.length !=0){
            for (int i = 1; i <= parameters.length; i++) {
                preparedStatement.setObject(i,parameters[i-1]);
            }
        }

        ResultSet resultSet = preparedStatement.executeQuery();

        //这个结果集中不装list<Map>了，而是装T的结果集
        List<T> list = new ArrayList<>();

        //获取列的数据
        ResultSetMetaData metaData = resultSet.getMetaData();

        int columnCount = metaData.getColumnCount();

        while (resultSet.next()){
            //一行数据，对应一个实体类T
            //调用类的无参构造函数实例化对象
            T t = clazz.newInstance();

            //按列遍历
            for (int i = 1; i <= columnCount; i++) {
                //对象的属性值
                Object value = resultSet.getObject(i);
                //对象的属性名
                String columnLabel = metaData.getColumnLabel(i);

                //反射，给对象的属性值赋值
                Field field = clazz.getDeclaredField(columnLabel);
                field.setAccessible(true);//属性可以设置，打破private的修饰限制
                /**
                 * 参数1：要赋值的对象，如果属性是静态属性，第一个参数可以为null
                 * 参数2：具体的属性值
                 */
                field.set(t,value);
            }
            //将t存储到集合中
            list.add(t);
        }
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()) {
            JDBCUtilsV2.freeConnection();
        }

        return list;
    }
}
